package com.snoob.springboot.util.jwt;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.snoob.springboot.base.BaseException;
import com.snoob.springboot.base.BaseLoginUserInfo;
import com.snoob.springboot.enums.SysKeyEnum;
import com.snoob.springboot.enums.SysStateEnum;
import com.snoob.springboot.util.LoggerUtil;

import java.text.MessageFormat;
import java.util.Date;
import java.util.UUID;

/**
 * @描述: JSON WEB TOKEN
 * @作者: lixing lixing_java@163.com
 * @日期 2020/3/4 16:25
 *
 * JWT是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器，服务器验证用户提交信息信息的合法性；
 * 如果验证成功，会产生并返回一个Token（令牌），用户可以使用这个token访问服务器上受保护的资源。
 *  第一部分：头部（header） {'typ':'JWT',//声明类型，这里是jwt'alg':'HS256'//声明加密的算法，通常直接使用 HMAC、SHA256、RSA}
 *  第二部分：载荷（payload，该token里携带的有效信息
 *  第三部分：签名（signature）
 *
 * OAuth简单说就是一种授权的协议，只要授权方和被授权方遵守这个协议去写代码提供服务，那双方就是实现了OAuth模式
 *  你想登录豆瓣去看看电影评论，但你丫的从来没注册过豆瓣账号，又不想新注册一个再使用豆瓣，怎么办呢？不用担心，豆瓣已经为你这种懒人做了准备，用你的qq号可以授权给豆瓣进行登录
 *  具体思路可以参考微信H5页面的静默授权
 */
public class JwtUtil {
    /**
     * 该 SECRET 默认为创建、解析 JWT的 HS256密钥。建议使用动态的值
     */
    private static final String SECRET = "25385a3d-2394-413d-a920-03e0bf25b81b";

    /**
     * @描述 以 HS256 的方式创建TOKEN
     * @作者 lixing
     * @日期 2020/4/3 13:35
     * @Param [jsonObjectParam]
     * @return java.lang.String
     */
    public static String createJwtForHs256(JSONObject jsonObjectParam) {
        if (jsonObjectParam == null) {
            LoggerUtil.error("createJwtForHMAC 创建token失败：入参为null");
            return null;
        }
        String token = null;
        try {
            // 创建时间
            long nowMillis = System.currentTimeMillis();
            Date createDate = new Date(nowMillis);
            // 生效时间，单位：毫秒
            long startExpires = jsonObjectParam.containsKey("startExpires") ? jsonObjectParam.getLong("startExpires") : 0;
            Date startDate = new Date(nowMillis + startExpires);
            // 过期时间， 单位：毫秒
            long endExpires = jsonObjectParam.containsKey("endExpires") ? jsonObjectParam.getLong("endExpires") : (1000 * 60 * 30);
            Date endDate = new Date(nowMillis + startExpires + endExpires);
            // 用户的授权信息
            String userInfoStr = jsonObjectParam.getString(SysKeyEnum.LOGIN_USERINFO.getKey());
            // 验证和签署算法
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            token = JWT.create()
                    .withJWTId(UUID.randomUUID().toString()) // 唯一身份标识，主要用来作为一次性token,从而回避重放攻击
                    .withIssuer("lixing_java@163.com") // 签发者
                    .withSubject("主题") // 主题
                    .withAudience("接收者") // 接收者
                    .withIssuedAt(createDate) // 签发时间
                    .withNotBefore(startDate) // 生效时间
                    .withExpiresAt(endDate) // 过期时间，这个过期时间必须要大于签发时间
                    .withClaim(SysKeyEnum.LOGIN_USERINFO.getKey(), userInfoStr) // 登陆成功的用户数据
                    .sign(algorithm);
        } catch (JWTCreationException e) {
            LoggerUtil.error(MessageFormat.format("createJwtForHMAC 创建token失败：{0}", e.getMessage()));
        }
        return token;
    }

    /**
     * @描述  以 HS256 的方式解析TOKEN
     * @作者 lixing
     * @日期 2020/4/3 13:54
     * @Param [jwt] 
     * @return com.alibaba.fastjson.JSONObject
     */
    public static BaseLoginUserInfo verifyJwtForHs256(String jwt) {
        try {
            // 验证和签署算法
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT decodedJwt = verifier.verify(jwt);
            //LoggerUtil.info("jwt-Token："+decodedJWT.getToken());
            // 头部信息，经过base64编码的JSON字符串（头部信息，经过base64编码的JSON字符串）
            //LoggerUtil.info("jwt-Header："+decodedJWT.getHeader());
            // 负载信息，经过base64编码的JSON字符串
            //LoggerUtil.info("jwt-Payload："+decodedJWT.getPayload());
            // 签名
            //LoggerUtil.info("jwt-Signature："+decodedJWT.getSignature());
            Claim claim = decodedJwt.getClaim(SysKeyEnum.LOGIN_USERINFO.getKey());
            String userInfoStr = claim.asString();
            //
            BaseLoginUserInfo baseLoginUserInfo = JSONObject.parseObject(userInfoStr, BaseLoginUserInfo.class);
            return baseLoginUserInfo;
        } catch (TokenExpiredException e) {
            throw new BaseException(SysStateEnum.RESPONSE_STATUS_FALSE_TOKEN_TIMEOUT.getValue(), SysStateEnum.RESPONSE_STATUS_FALSE_TOKEN_TIMEOUT.getDescribe());
        } catch (JWTVerificationException e) {
            throw new BaseException(SysStateEnum.RESPONSE_STATUS_FALSE_TOKEN_ERROR.getValue(), SysStateEnum.RESPONSE_STATUS_FALSE_TOKEN_ERROR.getDescribe());
        } catch (Exception e) {
            throw new BaseException("解析TOKEN时发生未知异常");
        }
    }

    public static void main(String[] args) {
        // 认证的用户信息
        BaseLoginUserInfo baseLoginUserInfo = new BaseLoginUserInfo();
        baseLoginUserInfo.setUserAccount("admin");
        baseLoginUserInfo.setUserPhone("18013896707");

        JSONObject jwt = new JSONObject();
        jwt.put("startExpires", 1000);
        jwt.put("endExpires", 1000 * 60 * 60 * 499);
        jwt.put(SysKeyEnum.LOGIN_USERINFO.getKey(), JSONObject.toJSONString(baseLoginUserInfo));
        String token = createJwtForHs256(jwt);
        System.out.println(token);
        BaseLoginUserInfo baseLoginUserInfo1 = JwtUtil.verifyJwtForHs256(token);
        System.out.println(baseLoginUserInfo1);
    }
}
